package com.night.ibatis;

import com.night.annotation.Param;
import com.night.annotation.Select;
import com.night.ibatis.typeHandler.IntegerTypeHandler;
import com.night.ibatis.typeHandler.LongTypeHandler;
import com.night.ibatis.typeHandler.StringTypeHandler;
import com.night.ibatis.typeHandler.TypeHandler;
import com.night.utils.StringUtils;

import javax.sql.DataSource;
import java.lang.reflect.*;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: chen yang
 * @Date: 2024/12/12 20:07
 */
public class MapperInvocationHandler implements InvocationHandler {

    final DataSource dataSource;

    public MapperInvocationHandler(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public static Map<Class, TypeHandler> typeHandlerMap = new HashMap<>();

    static {
        typeHandlerMap.put(String.class, new StringTypeHandler());
        typeHandlerMap.put(Integer.class, new IntegerTypeHandler());
        typeHandlerMap.put(Long.class, new LongTypeHandler());
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Connection connection = dataSource.getConnection();

        Select select = method.getAnnotation(Select.class);

        if (select == null) {
            return null;
        }

        String originSql = select.value();

        // 解析参数
        HashMap<String, Object> paramValueMapping = this.getParamValueMapping(method, args);

        PreparedStatement statement = this.buildStatement(connection, originSql, paramValueMapping);

        statement.execute();

        Class<?> resultType = this.getResultType(method);

        if (resultType == null) {
            return null;
        }

        // 处理返回类型
        ResultSet resultSet = statement.getResultSet();

        // 获取查询出来的字段名
        ResultSetMetaData metaData = resultSet.getMetaData();
        List<String> columnNames = new ArrayList<>();
        for (int i = 0; i < metaData.getColumnCount(); i++) {
            columnNames.add(metaData.getColumnName(i + 1));
        }

        Map<String, Method> setterMethodMap = getSetterMethodMap(resultType);

        List<Object> list = new ArrayList<>();

        while (resultSet.next()) {

            Object instance = resultType.newInstance();

            for (String columnName : columnNames) {
                Method setterMethod = setterMethodMap.getOrDefault(columnName, setterMethodMap.get(StringUtils.toLowerCamelCase(columnName)));
                if (setterMethod == null) {
                    continue;
                }
                TypeHandler<?> typeHandler = typeHandlerMap.get(setterMethod.getParameters()[0].getType());
                setterMethod.invoke(instance, typeHandler.getResult(resultSet, columnName));
            }
            list.add(instance);
        }

        if (method.getReturnType().equals(List.class)) {
            return list;
        }

        return list.getFirst();
    }


    /**
     * 获取方法参数名和实际的值
     */
    private HashMap<String, Object> getParamValueMapping(Method method, Object[] args) {
        HashMap<String, Object> paramValueMapping = new HashMap<>();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            // parameter.getName() 拿到的 arg[?] 而不是参数名
            paramValueMapping.put(parameter.getName(), args[i]);

            Param param = parameter.getAnnotation(Param.class);
            if (param != null) {
                paramValueMapping.put(param.value(), args[i]);
            }
        }

        return paramValueMapping;
    }

    private PreparedStatement buildStatement(Connection connection, String sql, HashMap<String, Object> paramValueMapping) throws SQLException {
        ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser parser = new GenericTokenParser("#{", "}", tokenHandler);

        String parsedSql = parser.parse(sql);
        List<ParameterMapping> parameterMappings = tokenHandler.getParameterMappings();

        System.out.println("sql=>: " + parsedSql);

        PreparedStatement statement = connection.prepareStatement(parsedSql);

        this.setStatementParameter(statement, parameterMappings, paramValueMapping);
        return statement;
    }

    /**
     * 设置sql参数值
     */
    private void setStatementParameter(PreparedStatement statement, List<ParameterMapping> parameterMappings, HashMap<String, Object> paramValueMapping) throws SQLException {
        for (int i = 0; i < parameterMappings.size(); i++) {
            String property = parameterMappings.get(i).getProperty();
            Object value = paramValueMapping.get(property);
            Class<?> paramValueClass = value.getClass();

            typeHandlerMap.get(paramValueClass).setParameter(statement, i + 1, value);
        }
    }


    /**
     * 获取方法返回值
     */
    private Class<?> getResultType(Method method) {
        Class<?> resultType = null;
        Type genericReturnType = method.getGenericReturnType();
        if (genericReturnType instanceof Class) {
            resultType = (Class<?>) genericReturnType;
        } else if (genericReturnType instanceof ParameterizedType) {
            Type actualTypeArgument = ((ParameterizedType) genericReturnType).getActualTypeArguments()[0];
            resultType = (Class<?>) actualTypeArgument;
        }
        return resultType;
    }


    /**
     * 获取 class 中的set方法
     */
    private Map<String, Method> getSetterMethodMap(Class<?> resultType) {

        Map<String, Method> setterMethodMap = new HashMap<>();

        for (Method declaredMethod : resultType.getDeclaredMethods()) {
            if (declaredMethod.getName().startsWith("set")) {
                String propertyName = declaredMethod.getName().substring(3);
                propertyName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1);
                setterMethodMap.put(propertyName, declaredMethod);
            }
        }
        return setterMethodMap;

    }

}
